﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal class ListDataItemIndexConfiguration
  {
    #region Constructor

    internal ListDataItemIndexConfiguration(
      IEnumerable<SortDescription> sortOrder,
      IEnumerable<IComparer> groupValueComparers,
      Predicate<object> filter,
      FilterExpressionBase filterExpression,
      Type elementType,
      Func<string, Type> getElementMemberType )
    {
      if( sortOrder == null )
        throw new ArgumentNullException( "sortOrder" );

      if( groupValueComparers == null )
        throw new ArgumentNullException( "groupValueComparers" );

      if( elementType == null )
        throw new ArgumentNullException( "elementType" );

      if( getElementMemberType == null )
        throw new ArgumentNullException( "getElementMemberType" );

      IList<SortDescription> sortOrderList = sortOrder.ToList();

      if( sortOrderList.Count <= 0 )
        throw new DataInternalException();

      IList<IComparer> comparers = ListDataItemIndexConfiguration.CreateGroupValueComparers(
                                     sortOrderList,
                                     groupValueComparers ).ToList();

      m_sortOrder = new ReadOnlyCollection<SortDescription>( sortOrderList );
      m_groupValueComparers = new ReadOnlyCollection<IComparer>( comparers );
      m_itemComparer = ListDataItemIndexConfiguration.CreateItemComparer( sortOrderList, comparers );
      m_filter = filter;
      m_filterExpression = filterExpression;
      m_predicate = ListDataItemIndexConfiguration.CreateFilterPredicate(
                      filter,
                      filterExpression,
                      elementType,
                      getElementMemberType );
    }

    #endregion

    #region SortOrder Property

    internal IList<SortDescription> SortOrder
    {
      get
      {
        return m_sortOrder;
      }
    }

    private ReadOnlyCollection<SortDescription> m_sortOrder;

    #endregion

    #region GroupValueComparers Property

    internal IList<IComparer> GroupValueComparers
    {
      get
      {
        return m_groupValueComparers;
      }
    }

    private ReadOnlyCollection<IComparer> m_groupValueComparers;

    #endregion

    #region ItemComparer Property

    internal IComparer<RawDataItem> ItemComparer
    {
      get
      {
        return m_itemComparer;
      }
    }

    private IComparer<RawDataItem> m_itemComparer;

    #endregion

    #region Filter Property

    internal Predicate<object> Filter
    {
      get
      {
        return m_filter;
      }
    }

    private Predicate<object> m_filter; //null


    #endregion

    #region FilterExpression Property

    internal FilterExpressionBase FilterExpression
    {
      get
      {
        return m_filterExpression;
      }
    }

    private FilterExpressionBase m_filterExpression; //null

    #endregion

    internal Func<RawDataItem, bool> CreatePredicate()
    {
      return m_predicate;
    }

    private static Func<RawDataItem, bool> CreateFilterPredicate(
      Predicate<object> filter,
      FilterExpressionBase filterExpression,
      Type elementType,
      Func<string, Type> getElementMemberType )
    {
      if( elementType == null )
        throw new ArgumentNullException( "elementType" );

      if( getElementMemberType == null )
        throw new ArgumentNullException( "getElementMemberType" );

      Func<RawDataItem, bool> filterPredicate = null;
      if( filter != null )
      {
        filterPredicate = ( RawDataItem item ) => filter.Invoke( item.Data );
      }

      Func<RawDataItem, bool> filterExpressionPredicate = null;
      if( filterExpression != null )
      {
        PredicateBuilder pb = new PredicateBuilder( filterExpression );
        Func<object, bool> newFilterExpressionPredicate = pb.ToPredicate<object>( elementType, getElementMemberType );

        filterExpressionPredicate = ( RawDataItem item ) => newFilterExpressionPredicate.Invoke( item );
      }

      if( ( filterPredicate != null ) && ( filterExpressionPredicate != null ) )
      {
        return ( RawDataItem item ) => filterPredicate.Invoke( item )
                                    && filterExpressionPredicate.Invoke( item );
      }
      else if( filterPredicate != null )
      {
        return filterPredicate;
      }
      else if( filterExpressionPredicate != null )
      {
        return filterExpressionPredicate;
      }
      else
      {
        return null;
      }
    }

    private static IEnumerable<IComparer> CreateGroupValueComparers(
      IList<SortDescription> sortOrder,
      IEnumerable<IComparer> comparers )
    {
      if( sortOrder.Count != comparers.Count() )
        throw new DataInternalException();

      int i = 0;
      foreach( IComparer comparer in comparers )
      {
        // Reverse the comparer result if the level is sorted in reverse order.
        IComparer newComparer = comparer;
        if( sortOrder[ i ].Direction == ListSortDirection.Descending )
        {
          newComparer = new ReverseComparer( newComparer );
        }

        yield return newComparer;
        i++;
      }
    }

    private static IComparer<RawDataItem> CreateItemComparer( IList<SortDescription> sortOrder, IList<IComparer> comparers )
    {
      if( comparers.Count <= 0 )
        throw new ArgumentException( "At least one comparer must be specified.", "comparers" );

      if( sortOrder.Count != comparers.Count )
        throw new ArgumentException( "One comparer is required per sorted property.", "comparers" );

      IComparer<RawDataItem> comparer = new RawDataItemComparer( comparers[ 0 ], sortOrder[ 0 ].PropertyName );
      for( int i = 1; i < comparers.Count; i++ )
      {
        IComparer<RawDataItem> levelValueComparer = new RawDataItemComparer( comparers[ i ], sortOrder[ i ].PropertyName );
        comparer = new CombineComparer( comparer, levelValueComparer );
      }

      return comparer;
    }

    #region Private Fields

    private Func<RawDataItem, bool> m_predicate; //null

    #endregion

    #region Private ReverseComparer Nested Type

    private class ReverseComparer : IComparer
    {
      internal ReverseComparer( IComparer comparer )
      {
        if( comparer == null )
          throw new ArgumentNullException( "comparer" );

        m_comparer = comparer;
      }

      public int Compare( object x, object y )
      {
        return -m_comparer.Compare( x, y );
      }

      private IComparer m_comparer;
    }

    #endregion

    #region Private CombineComparer Nested Type

    private class CombineComparer : IComparer<RawDataItem>, IComparer
    {
      internal CombineComparer( IComparer<RawDataItem> first, IComparer<RawDataItem> second )
      {
        if( first == null )
          throw new ArgumentNullException( "first" );

        if( second == null )
          throw new ArgumentNullException( "second" );

        m_first = first;
        m_second = second;
      }

      public int Compare( RawDataItem x, RawDataItem y )
      {
        if( x == null )
          throw new ArgumentNullException( "x" );

        if( y == null )
          throw new ArgumentNullException( "y" );

        int compare = m_first.Compare( x, y );
        if( compare != 0 )
          return compare;

        return m_second.Compare( x, y );
      }

      public int Compare( object x, object y )
      {
        return this.Compare( x as RawDataItem, y as RawDataItem );
      }

      private IComparer<RawDataItem> m_first;
      private IComparer<RawDataItem> m_second;
    }

    #endregion

    #region Private RawDataItemComparer Nested Type

    private class RawDataItemComparer : IComparer<RawDataItem>, IComparer
    {
      internal RawDataItemComparer( IComparer comparer, string memberName )
      {
        if( comparer == null )
          throw new ArgumentNullException( "comparer" );

        m_comparer = comparer;
        m_memberName = memberName;
      }

      public int Compare( RawDataItem x, RawDataItem y )
      {
        if( x == null )
          throw new ArgumentNullException( "x" );

        if( y == null )
          throw new ArgumentNullException( "y" );

        object firstValue = x.GetDataMemberValue( m_memberName );
        object secondValue = y.GetDataMemberValue( m_memberName );

        return m_comparer.Compare( firstValue, secondValue );
      }

      public int Compare( object x, object y )
      {
        return this.Compare( x as RawDataItem, y as RawDataItem );
      }

      private IComparer m_comparer;
      private string m_memberName;
    }

    #endregion
  }
}
